﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.IO;

namespace cs2335lab5
{
    namespace Server
    {
        /// <summary>
        /// This class manages a collection of users logged into our server.
        /// </summary>
        [XmlRoot("UserManager")]
        [XmlInclude(typeof(Data.User))]
        public class UserManager
        {
            /// <summary>
            /// A quick way to look up a user by their user id.  Key=user id, Value=user class for that id
            /// </summary>
            private Dictionary<string, Data.User> users;

            /// <summary>
            /// Note that we cannot serialize a generic dictionary.
            /// </summary>
            [XmlIgnore]
            public Dictionary<string, Data.User> Users
            {
                get { return users; }
                set { users = value; }
            }

            /// <summary>
            /// Use LINQ to get a list of the online users
            /// </summary>
            [XmlIgnore]
            public List<Data.User> OnlineUsers
            {
                get
                {
                    return (from entry in Users
                        where entry.Value.isLoggedIn
                        select entry.Value).ToList();
                }
            }

            /// <summary>
            /// Property created to fool xml serialization
            /// </summary>
            [XmlArray("Users")]
            [XmlArrayItem("UsersLine", Type = typeof(DictionaryEntry))]
            public DictionaryEntry[] UserArray
            {
                get
                {
                    //Make an array of DictionaryEntries to return 
                    DictionaryEntry[] ret = new DictionaryEntry[Users.Count];
                    int i = 0;
                    DictionaryEntry de;
                    //Iterate through Stuff to load items into the array. 
                    foreach (KeyValuePair<string, Data.User> usersLine in Users)
                    {
                        de = new DictionaryEntry();
                        de.Key = usersLine.Key;
                        de.Value = usersLine.Value;
                        ret[i] = de;
                        i++;
                    }
                    return ret;
                }
                set
                {
                    Users.Clear();
                    for (int i = 0; i < value.Length; i++)
                    {
                        Users.Add((string)value[i].Key, (Data.User)value[i].Value);
                    }
                }
            }

            /// <summary>
            /// Constructor, makes a new instance of class
            /// </summary>
            public UserManager()
            {
                users = new Dictionary<string, Data.User>();
            }

            /// <summary>
            /// Given a user id and password, add a new user to the collection of our users
            /// Returns a reference to the user created in the out parameter.
            /// </summary>
            /// <param name="name">The user id of the new user</param>
            /// <param name="pass">The password of the new user</param>
            /// <returns>true if that user name does not already exist, false otherwise</returns>
            public Data.User AddUser(Data.User actor, Data.User user)
            {
                // check if the user is already in the dictonary
                if (!users.ContainsKey(user.ID))
                {
                    // if it isn't, add the user to the dictionary

                    // if the adding user is normal, only let him create a normal user
                    // if actor is null, it's a call to register
                    if ((actor != null &&
                        user.Access == Data.User.Privileges.Admin &&
                        FindUser(actor.ID).Access != Data.User.Privileges.Admin)
                        || (actor == null))
                    {
                        user.Access = Data.User.Privileges.Normal;
                    }

                    // add now!
                    Users.Add(user.ID, user);

                    // serialize the user list
                    Serialize();
                    return user;
                }

                // go away, you dirty hobo
                return null;
            }

            /// <summary>
            /// Update information on a user, including their access rights
            /// </summary>
            /// <param name="actor"></param>
            /// <param name="reciever"></param>
            public Data.User UpdateUser(Data.User actor, Data.User reciever)
            {
                // get our data on the actor
                Data.User myActor = FindUser(actor.ID);

                // get our data on the receiver
                Data.User myReciever = FindUser(reciever.ID);

                if (myActor != null && myReciever == null)
                {
                    return AddUser(actor, reciever);
                }

                // if the actor is the reciever, or if the actor is an admin, proceed
                if (myActor.ID.Equals(reciever.ID) ||
                    myActor.Access == Data.User.Privileges.Admin)
                {
                    // set the provided data
                    myReciever.Name = reciever.Name;
                    myReciever.Password = reciever.Password;
                    // a normal user can't change another's access
                    if (myReciever.Access != reciever.Access &&
                        actor.Access == Data.User.Privileges.Admin)
                    {
                        myReciever.Access = reciever.Access;
                    }
                    myReciever.Email = reciever.Email;
                }

                return myReciever;
            }

            /// <summary>
            /// Look up a user id and find it in the dictionary
            /// </summary>
            /// <param name="name">The user id to look up</param>
            /// <returns>the user instance for that user if found, null otherwise</returns>
            public Data.User FindUser(string name)
            {
                //App.WriteLine("Looking up: " + name);
                if (users.ContainsKey(name))
                    return users[name];
                else
                    return null;
            }

            /// <summary>
            /// Try to login a user.  Lookup the user id.  If not found, user doesn't exist.  If found, check the
            /// password to ensure a match.
            /// </summary>
            /// <param name="name">The user id</param>
            /// <param name="pass">The password</param>
            /// <returns>full user details if successful, null if unsuccessful</returns>
            public Data.User Authenticate(Data.User provided)
            {
                App.WriteLine("Authenticating user: " + provided.ID);

                // look up a user by the provided name
                Data.User user = FindUser(provided.ID);

                // check if the user exists; if that passes, check if the passwords match
                if (user == null || user.Password.CompareTo(provided.Password) != 0)
                {
                    return null;
                }

                // passwords match, login successful
                App.WriteLine("Logged in: " + provided.ID);
                user.isLoggedIn = true;

                return user;
            }

            public bool AddCallback(Data.User provided, Data.INetworkManagerCallback callback)
            {
                App.WriteLine("Storing callback for user: " + provided.ID);

                // look up a user by the provided name
                Data.User user = FindUser(provided.ID);

                if( user == null)
                {
                    return false;
                }

                user.Callback = callback;

                return true;
            }

            /// <summary>
            /// Logs a user out of the server
            /// </summary>
            /// <param name="provided"></param>
            public void LogOut(Data.User provided)
            {
                App.WriteLine("Logging out user: " + provided.ID);

                // look up a user by the provided name
                Data.User user = FindUser(provided.ID);

                // check if the user exists
                if (user != null)
                {
                    user.isLoggedIn = false;
                }
            }

            /// <summary>
            /// Deletes a user with a specific ID
            /// </summary>
            /// <param name="id"></param>
            public void Delete(string id)
            {
                if (id == null)
                {
                    return;
                }

                App.WriteLine("Deleting: " + id);

                // look up a user by the provided name
                Data.User user = FindUser(id);

                // check if user exists
                if (user != null)
                {
                    // log the user out
                    if (user.isLoggedIn)
                    {
                        LogOut(user);
                    }

                    // remove it if it does
                    users.Remove(id);

                    // serialize users
                    Serialize();
                }
            }

            /// <summary>
            /// Serialize the list of registered users. This method runs every time a new user is added.
            /// </summary>
            public void Serialize()
            {
                XmlSerializer serializer = new XmlSerializer(typeof(UserManager));
                string filename = "users.xml";
                StreamWriter outStream = new StreamWriter(filename, false);
                serializer.Serialize(outStream, this);
                outStream.Close();
            }
        }
    }
}